# EntityDecoratedEmbeddableConfigurationSupport.java

    package org.codefilarete.stalactite.engine.configurer.entity;

    import java.util.function.BiFunction;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;

    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.stalactite.dsl.ExtraTablePropertyOptions;
    import org.codefilarete.stalactite.dsl.entity.FluentEntityMappingBuilder;
    import org.codefilarete.stalactite.dsl.idpolicy.IdentifierPolicy;
    import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfigurationProvider;
    import org.codefilarete.stalactite.dsl.key.CompositeKeyOptions;
    import org.codefilarete.stalactite.dsl.key.FluentEntityMappingBuilderCompositeKeyOptions;
    import org.codefilarete.stalactite.dsl.key.FluentEntityMappingBuilderKeyOptions;
    import org.codefilarete.stalactite.dsl.key.KeyOptions;
    import org.codefilarete.stalactite.dsl.property.ColumnOptions;
    import org.codefilarete.stalactite.dsl.property.PropertyOptions;
    import org.codefilarete.stalactite.engine.configurer.embeddable.FluentEmbeddableMappingConfigurationSupport;
    import org.codefilarete.stalactite.engine.configurer.embeddable.LinkageSupport;
    import org.codefilarete.stalactite.engine.configurer.property.ColumnLinkageOptionsByColumn;
    import org.codefilarete.stalactite.engine.configurer.property.ColumnLinkageOptionsSupport;
    import org.codefilarete.stalactite.sql.ddl.Size;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinder;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.function.Converter;
    import org.codefilarete.tool.function.TriFunction;
    import org.codefilarete.tool.reflect.MethodDispatcher;
    import org.danekja.java.util.function.serializable.SerializableBiConsumer;
    import org.danekja.java.util.function.serializable.SerializableFunction;

    /**
     * Class very close to {@link FluentEmbeddableMappingConfigurationSupport}, but with dedicated methods to entity mapping such as
     * identifier definition or configuration override by {@link Column}
     */
    class EntityDecoratedEmbeddableConfigurationSupport<C, I> extends FluentEmbeddableMappingConfigurationSupport<C> {

    	private final FluentEntityMappingConfigurationSupport<C, I> entityConfigurationSupport;

    	/**
    	 * Creates a builder to map the given class for persistence
    	 *
    	 * @param persistedClass the class to create a mapping for
    	 */
    	public EntityDecoratedEmbeddableConfigurationSupport(FluentEntityMappingConfigurationSupport<C, I> entityConfigurationSupport, Class<C> persistedClass) {
    		super(persistedClass);
    		this.entityConfigurationSupport = entityConfigurationSupport;
    	}

    	<E> LinkageSupport<C, E> addMapping(SerializableBiConsumer<C, E> setter) {
    		LinkageSupport<C, E> newLinkage = new LinkageSupport<>(setter);
    		mapping.add(newLinkage);
    		return newLinkage;
    	}

    	<E> LinkageSupport<C, E> addMapping(SerializableFunction<C, E> getter) {
    		LinkageSupport<C, E> newLinkage = new LinkageSupport<>(getter);
    		mapping.add(newLinkage);
    		return newLinkage;
    	}

    	<E> LinkageSupport<C, E> addMapping(String fieldName) {
    		LinkageSupport<C, E> newLinkage = new LinkageSupport<>(getEntityType(), fieldName);
    		mapping.add(newLinkage);
    		return newLinkage;
    	}

    	public <O> FluentEntityMappingBuilder.FluentMappingBuilderPropertyOptions<C, I, O> wrapWithAdditionalPropertyOptions(LinkageSupport<C, O> newMapping) {
    		return new MethodDispatcher()
    				.redirect(ColumnOptions.class, new ColumnOptions<O>() {
    					@Override
    					public ColumnOptions<O> mandatory() {
    						newMapping.setNullable(false);
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> nullable() {
    						newMapping.setNullable(true);
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> unique() {
    						newMapping.setUnique(true);
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> setByConstructor() {
    						newMapping.setByConstructor();
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> readonly() {
    						newMapping.readonly();
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> columnName(String name) {
    						newMapping.getColumnOptions().setColumnName(name);
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> columnSize(Size size) {
    						newMapping.getColumnOptions().setColumnSize(size);
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> column(Column<? extends Table, ? extends O> column) {
    						newMapping.setColumnOptions(new ColumnLinkageOptionsByColumn(column));
    						return null;
    					}

    					@Override
    					public ColumnOptions<O> fieldName(String name) {
    						newMapping.setField(EntityDecoratedEmbeddableConfigurationSupport.this.entityConfigurationSupport.getEntityType(), name);
    						return null;
    					}

    					@Override
    					public <X> ColumnOptions<O> readConverter(Converter<X, O> converter) {
    						newMapping.setReadConverter(converter);
    						return null;
    					}

    					@Override
    					public <X> ColumnOptions<O> writeConverter(Converter<O, X> converter) {
    						newMapping.setWriteConverter(converter);
    						return null;
    					}

    					@Override
    					public <V> PropertyOptions<O> sqlBinder(ParameterBinder<V> parameterBinder) {
    						newMapping.setParameterBinder(parameterBinder);
    						return null;
    					}
    				}, true)
    				.redirect(ExtraTablePropertyOptions.class, name -> {
    					newMapping.setExtraTableName(name);
    					return null;
    				}, true)
    				.fallbackOn(entityConfigurationSupport)
    				.build((Class<FluentEntityMappingBuilder.FluentMappingBuilderPropertyOptions<C, I, O>>) (Class) FluentEntityMappingBuilder.FluentMappingBuilderPropertyOptions.class);
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableFunction<C, I> getter, IdentifierPolicy<I> identifierPolicy) {
    		return addKeyMapping(new SingleKeyLinkageSupport<>(getter, identifierPolicy));
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableFunction<C, I> getter, IdentifierPolicy<I> identifierPolicy, Column<?, I> column) {
    		SingleKeyLinkageSupport<C, I> linkage = addKeyMapping(new SingleKeyLinkageSupport<>(getter, identifierPolicy));
    		linkage.setColumnOptions(new ColumnLinkageOptionsByColumn(column));
    		return linkage;
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableFunction<C, I> getter, IdentifierPolicy<I> identifierPolicy, String columnName) {
    		SingleKeyLinkageSupport<C, I> linkage = addKeyMapping(new SingleKeyLinkageSupport<>(getter, identifierPolicy));
    		linkage.setColumnOptions(new ColumnLinkageOptionsSupport(columnName));
    		return linkage;
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableBiConsumer<C, I> setter, IdentifierPolicy<I> identifierPolicy) {
    		return addKeyMapping(new SingleKeyLinkageSupport<>(setter, identifierPolicy));
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableBiConsumer<C, I> setter, IdentifierPolicy<I> identifierPolicy, Column<?, I> column) {
    		SingleKeyLinkageSupport<C, I> linkage = addKeyMapping(new SingleKeyLinkageSupport<>(setter, identifierPolicy));
    		linkage.setColumnOptions(new ColumnLinkageOptionsByColumn(column));
    		return linkage;
    	}

    	SingleKeyLinkageSupport<C, I> addKeyMapping(SerializableBiConsumer<C, I> setter, IdentifierPolicy<I> identifierPolicy, String columnName) {
    		SingleKeyLinkageSupport<C, I> linkage = addKeyMapping(new SingleKeyLinkageSupport<>(setter, identifierPolicy));
    		linkage.setColumnOptions(new ColumnLinkageOptionsSupport(columnName));
    		return linkage;
    	}

    	/**
    	 *
    	 * @param newLinkage
    	 * @return
    	 */
    	private SingleKeyLinkageSupport<C, I> addKeyMapping(SingleKeyLinkageSupport<C, I> newLinkage) {
    		// Please note that we don't check for any id presence in inheritance since this will override parent one (see final build())
    		if (entityConfigurationSupport.getKeyMapping() != null) {
    			throw new IllegalArgumentException("Identifier is already defined by " + AccessorDefinition.toString(entityConfigurationSupport.getKeyMapping().getAccessor()));
    		}
    		entityConfigurationSupport.setKeyMapping(newLinkage);
    		return newLinkage;
    	}

    	/**
    	 *
    	 * @param propertyAccessor
    	 * @return
    	 */
    	public CompositeKeyLinkageSupport<C, I> addCompositeKeyMapping(ReversibleAccessor<C, I> propertyAccessor,
    																   CompositeKeyMappingConfigurationProvider<I> compositeKeyMappingBuilder,
    																   Consumer<C> markAsPersistedFunction,
    																   Function<C, Boolean> isPersistedFunction) {
    		// Please note that we don't check for any id presence in inheritance since this will override parent one (see final build())
    		if (entityConfigurationSupport.getKeyMapping() != null) {
    			throw new IllegalArgumentException("Identifier is already defined by " + AccessorDefinition.toString(entityConfigurationSupport.getKeyMapping().getAccessor()));
    		}
    		CompositeKeyLinkageSupport<C, I> newLinkage = new CompositeKeyLinkageSupport<>(propertyAccessor, compositeKeyMappingBuilder, markAsPersistedFunction, isPersistedFunction);
    		entityConfigurationSupport.setKeyMapping(newLinkage);
    		return newLinkage;
    	}

    	public FluentEntityMappingBuilderKeyOptions<C, I> wrapWithKeyOptions(SingleKeyLinkageSupport<C, I> keyMapping) {
    		return new MethodDispatcher()
    				.redirect(KeyOptions.class, new KeyOptions<C, I>() {

    					@Override
    					public KeyOptions<C, I> columnName(String name) {
    						keyMapping.getColumnOptions().setColumnName(name);
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> columnSize(Size size) {
    						keyMapping.getColumnOptions().setColumnSize(size);
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> column(Column<? extends Table, ? extends I> column) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsByColumn(column));
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> fieldName(String name) {
    						keyMapping.setField(entityConfigurationSupport.getEntityType(), name);
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> usingConstructor(Supplier<C> factory) {
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(table -> row -> factory.get(), false));
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> usingConstructor(Function<? super I, C> factory) {
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(table -> {
    							Column<?, I> primaryKey = (Column<?, I>) Iterables.first(((Table<?>) table).getPrimaryKey().getColumns());
    							return row -> factory.apply((I) row.get(primaryKey));
    						}, true));
    						return null;
    					}

    					@Override
    					public <T extends Table<T>> KeyOptions<C, I> usingConstructor(Function<? super I, C> factory, Column<T, I> input) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsByColumn(input));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(table -> row -> factory.apply((I) row.get(input)), true));
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> usingConstructor(Function<? super I, C> factory, String columnName) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsSupport(columnName));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(table -> row -> factory.apply((I) row.get(table.getColumn(columnName))), true));
    						return null;
    					}

    					@Override
    					public <X, T extends Table<T>> KeyOptions<C, I> usingConstructor(BiFunction<? super I, X, C> factory,
    																					 Column<T, I> input1,
    																					 Column<T, X> input2) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsByColumn(input1));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(
    								table -> row -> factory.apply(
    										(I) row.get(input1),
    										(X) row.get(input2)),
    								true));
    						return null;
    					}

    					@Override
    					public <X> KeyOptions<C, I> usingConstructor(BiFunction<? super I, X, C> factory,
    																 String columnName1,
    																 String columnName2) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsSupport(columnName1));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(
    								table -> row -> factory.apply(
    										(I) row.get(table.getColumn(columnName1)),
    										(X) row.get(table.getColumn(columnName2))),
    								true));
    						return null;
    					}

    					@Override
    					public <X, Y, T extends Table<T>> KeyOptions<C, I> usingConstructor(TriFunction<? super I, X, Y, C> factory,
    																						Column<T, I> input1,
    																						Column<T, X> input2,
    																						Column<T, Y> input3) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsByColumn(input1));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(
    								table -> row -> factory.apply(
    										(I) row.get(input1),
    										(X) row.get(input2),
    										(Y) row.get(input3)),
    								true));
    						return null;
    					}

    					@Override
    					public <X, Y> KeyOptions<C, I> usingConstructor(TriFunction<? super I, X, Y, C> factory,
    																	String columnName1,
    																	String columnName2,
    																	String columnName3) {
    						keyMapping.setColumnOptions(new ColumnLinkageOptionsSupport(columnName1));
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(
    								table -> row -> factory.apply(
    										(I) row.get(table.getColumn(columnName1)),
    										(X) row.get(table.getColumn(columnName2)),
    										(Y) row.get(table.getColumn(columnName3))),
    								true));
    						return null;
    					}

    					@Override
    					public KeyOptions<C, I> usingFactory(Function<ColumnedRow, C> factory) {
    						keyMapping.setByConstructor();
    						entityConfigurationSupport.setEntityFactoryProvider(new EntityFactoryProviderSupport<>(table -> row -> (C) factory.apply(row), true));
    						return null;
    					}
    				}, true)
    				.fallbackOn(entityConfigurationSupport)
    				.build((Class<FluentEntityMappingBuilderKeyOptions<C, I>>) (Class) FluentEntityMappingBuilderKeyOptions.class);
    	}

    	public FluentEntityMappingBuilderCompositeKeyOptions<C, I> wrapWithKeyOptions(CompositeKeyLinkageSupport<C, I> keyMapping) {
    		return new MethodDispatcher()
    				.redirect(CompositeKeyOptions.class, new CompositeKeyOptions<C, I>() {

    				}, true)
    				.fallbackOn(entityConfigurationSupport)
    				.build((Class<FluentEntityMappingBuilderCompositeKeyOptions<C, I>>) (Class) FluentEntityMappingBuilderCompositeKeyOptions.class);
    	}
    }